// Copyright (c)  YISH. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using NSQL.Generator;
using NSQL.Resource;

namespace NSQL.Variables
{
    public class VariableSpace
    {
        private readonly List<TableInfo> _tables = new List<TableInfo>();
        private readonly SqlGeneratorSettings _settings;
        private readonly HashSet<string> _validTableNames = new HashSet<string>();
        private readonly char _level = '`';
        
        private HashSet<object> _upLevel = new HashSet<object>();
        
        private const string AliasPrefix = "t";
        private int _aliasCount = 1;
        private int _tableCount = -1;
        public VariableSpace ParentSpace { get; }
        public IReadOnlyList<TableInfo> Tables => _tables;

        public IEnumerable<TableInfo> GetAllTables()
        {
            foreach (var table in _tables)
            {
                yield return table;
            }

            if (ParentSpace == null) yield break;
            foreach (var table in ParentSpace.Tables)
            {
                yield return table;
            }
        }

        public void SetTableCount(int count)
        {
            if (_tableCount == -1)
            {
                _tableCount = count;
            }
            else
            {
                throw new InvalidOperationException("The TableCount has been set.");
            }
        }

        public TableInfo GetTable(string tableName)
        {
            return GetAllTables().First(o => tableName == o.Name);
        }

        public TableInfo AppendTable(TableInfo table)
        {
            if (string.IsNullOrWhiteSpace(table.Alias))
            {
                table.Alias = null;
            }

            if (_settings.AutoAlias && table.Alias == null && (_tableCount > 1 || !IsTableAliasValid(table.Name)))
            {
                table.Alias = GetAvailableAlias();
            }
            var tableName = table.Alias ?? table.Name;
            if (IsTableAliasValid(tableName))
            {
                _validTableNames.Add(tableName);
            }
            _tables.Add(table);
            return table;
        }

        public void UpLevel(object obj)
        {
            if (_upLevel.Contains(obj)) throw new InvalidOperationException();
            _upLevel.Add(obj);
        }

        public bool NeedUpLevel(object obj) => _upLevel.Contains(obj);
        public void DownLevel(object obj) => _upLevel.Remove(obj);

        public VariableSpace(SqlGeneratorSettings settings, VariableSpace parent = null)
        {
            _settings = settings ?? throw new ArgumentNullException(nameof(settings));
            ParentSpace = parent;

            while (parent != null)
            {
                if (_level == 'z') throw new NotSupportedException();
                _level++;
                parent = parent.ParentSpace;
            }
        }

        private string GetAvailableAlias()
        {
            while (true)
            {
                var alias = _level == '`' ? $"{AliasPrefix}{_aliasCount++}" : $"{AliasPrefix}{_level}{_aliasCount++}";
                
                if (IsTableAliasValid(alias)) return alias;
            }
        }

        private bool IsTableAliasValid(string aliasName)
        {
            if (_validTableNames.Contains(aliasName)) return false;
            return ParentSpace?.IsTableAliasValid(aliasName) ?? true;
        }
    }
}